using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using RPGOnline.Controls.Map.Layers;
using RPGOnline.Rendering;

namespace RPGOnline.Controls.Map
{
    public class MapTileset
    {
        private readonly Device m_device;
        private readonly Bitmap m_tileSetImage;
        private readonly Size m_tileSize;

        private MapCell[,] m_tiles;
        //private Texture m_texture;

        public MapCell[,] Tiles { get { return m_tiles; } }
        public int Width { get; private set; }
        public int Height { get; private set; }
        public int PixelWidth { get { return Width*m_tileSize.Width; } }
        public int PixelHeight { get { return Height*m_tileSize.Height; } }
        public Size TileSize { get { return m_tileSize; } }
        public Bitmap TileSetImage { get { return m_tileSetImage; } }

        public MapTileset(Renderer renderer, Bitmap tileSetImage, Size tileSize, int framesPerTile = 1)
        {
            m_device = renderer.Device;
            m_tileSetImage = tileSetImage;
            m_tileSize = tileSize;

            Width = (tileSetImage.Width/tileSize.Width)/framesPerTile;
            Height = (tileSetImage.Height/tileSize.Height);

            m_tiles = new MapCell[Width, Height];

            int texturesNeeded = (tileSetImage.Height/m_device.DeviceCaps.MaxTextureHeight) + 1;
            int lastTileRow = 0;

            for (int i = 0; i < texturesNeeded; i++)
            {
                int linesForThisTexture;
                Texture texture = CreateTextureForTilesetPart(tileSetImage, lastTileRow, out linesForThisTexture);

                for (int x = 0; x < Width; x++)
                {
                    int textureY = 0;
                    for (int y = lastTileRow; y < lastTileRow + linesForThisTexture; y++, textureY++)
                    {
                        m_tiles[x, y] = new MapCell();

                        for (int f = 0; f < framesPerTile; f++)
                        {
                            m_tiles[x, y].Frames.Add(
                                new MapCellFrame(
                                    new Point((x + Width * f) * tileSize.Width, textureY * tileSize.Height),
                                    this,
                                    texture
                                    )
                                );
                        }
                    }
                }

                lastTileRow += linesForThisTexture;
            }
        }

        private Texture CreateTextureForTilesetPart(Bitmap tileSetImage, int lastTileRow, out int linesForThisTexture)
        {
            int totalTileLines = tileSetImage.Height/m_tileSize.Height;
            int maxTileLinesInDeviceTexture = m_device.DeviceCaps.MaxTextureHeight/m_tileSize.Height;
            int remainingLines = totalTileLines - lastTileRow;
            linesForThisTexture = Math.Min(remainingLines, maxTileLinesInDeviceTexture);

            int textureHeight = linesForThisTexture*m_tileSize.Height;
            Texture texture = new Texture(m_device, tileSetImage.Width, textureHeight, 1, Usage.None, Format.A8R8G8B8, Pool.Managed);

            int pitch;
            using (GraphicsStream graphicsStream = texture.LockRectangle(0, LockFlags.None, out pitch))
            {
                BitmapData bitmapData = tileSetImage.LockBits(new Rectangle(0, lastTileRow * m_tileSize.Height, tileSetImage.Width, textureHeight),
                                                              ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                for (int scanLine = 0; scanLine < textureHeight; scanLine++)
                {
                    IntPtr startOfLine = bitmapData.Scan0 + scanLine * bitmapData.Stride;

                    byte[] data = new byte[bitmapData.Stride];
                    Marshal.Copy(startOfLine, data, 0, bitmapData.Stride);
                    graphicsStream.Write(data);
                    graphicsStream.Seek(scanLine*pitch, SeekOrigin.Begin);
                }

                tileSetImage.UnlockBits(bitmapData);
                texture.UnlockRectangle(0);
            }

            return texture;
        }

        public void Draw(Graphics graphics)
        {
            for (int x = 0; x < Width; x++)
            {
                for (int y = 0; y < Height; y++)
                {
                    Rectangle destinationRectangle = new Rectangle(x * m_tileSize.Width, y * m_tileSize.Height, m_tileSize.Width, m_tileSize.Height);
                    MapCell tile = m_tiles[x, y];

                    if (tile.Frame == null)
                        return;

                    graphics.DrawImage(m_tileSetImage, destinationRectangle, destinationRectangle, GraphicsUnit.Pixel);

                    foreach (ObstructionLine obstructionLine in tile.ObstructionLines)
                    {
                        foreach (ObstructionEdge obstructionEdge in obstructionLine.GetEdges(destinationRectangle.X, destinationRectangle.Y))
                        {
                            graphics.DrawLine(Pens.White,
                                obstructionEdge.StartPoint.X,
                                obstructionEdge.StartPoint.Y,
                                obstructionEdge.EndPoint.X,
                                obstructionEdge.EndPoint.Y);
                        }
                    }
                }
            }
        }
    }
}